﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using MadLibrary.Net.Client;

namespace MadLibrary.Net.Http
{
    /// <summary>
    /// Parses, holds and builds normal HTTP web request.
    /// </summary>
    public class HttpRequest : IBuildable, IParsable
    {
        /// <summary>
        /// Represents request method (like GET, POST etc.).
        /// </summary>
        public string Method { get; set; }
        /// <summary>
        /// Gets or sets requests' HTTP protocol version.
        /// </summary>
        public string HttpVersion { get; set; }
        /// <summary>
        /// Gets or sets requested file name and web path.
        /// </summary>
        public string RequestedFile { get; set; }
        /// <summary>
        /// Holds options of request.
        /// </summary>
        public Dictionary<string, string> Options { get; set; }
        /// <summary>
        /// Holds data of current request.
        /// </summary>
        public Dictionary<string, string> Data { get; set; }
        /// <summary>
        /// Creates new HttpRequest object.
        /// </summary>
        public HttpRequest()
        {
            this.Options = new Dictionary<string, string>();
            this.Method = "";
            this.HttpVersion = "";
            this.RequestedFile = "";
            this.Data = new Dictionary<string, string>();
        }
        /// <summary>
        /// Creates new request object and parses request from specified string.
        /// </summary>
        /// <param name="toParse">Specifies string which has to be parsed.</param>
        public HttpRequest(string toParse)
        {
            this.Options = new Dictionary<string, string>();
            this.Data = new Dictionary<string, string>();
            this.Parse(toParse);
        }
        /// <summary>
        /// Parses values from string to request.
        /// </summary>
        /// <param name="toParse">Specifies string which has to be parsed.</param>
        public void Parse(string toParse)
        {
            Regex r = new Regex("(\\w*) (.*) HTTP/(.\\..)\\r\\n(.*)", RegexOptions.Singleline);
            Match m = r.Match(toParse);
            if (!m.Success)
                throw new InvalidRequestInput();
            this.Method = m.Groups[1].Value;
            this.RequestedFile = m.Groups[2].Value;
            this.HttpVersion = m.Groups[3].Value;
            MatchCollection mc = new Regex("(.*): (.*)\\r\\n").Matches(m.Groups[4].Value);
            foreach (Match a in mc)
            {
                this.Options.Add(a.Groups[1].Value, a.Groups[2].Value);
            }
            if (this.Method == "GET")
            {
                if (this.RequestedFile.Contains("?"))
                {
                    string[] prts = this.RequestedFile.Split('?');
                    if (prts[1].Contains("&"))
                    {
                        string[] es = prts[1].Split(new char[] { '&' }, StringSplitOptions.RemoveEmptyEntries);
                        foreach (string e in es)
                        {
                            this.Data.Add(e.Split('=')[0], e.Split('=')[1]);
                        }
                    }
                    else
                        this.Data.Add(prts[1].Split('=')[0], prts[1].Split('=')[1]);
                    this.RequestedFile = prts[0];
                }
            }
            else
            {
                Regex s = new Regex("\r\n(.*=.*)\r\n");
                Match g = s.Match(toParse);
                if (g.Success)
                {
                    if (g.Groups[1].Value.Contains("&"))
                    {
                        string[] conds = g.Groups[1].Value.Split(new char[]{'&'}, StringSplitOptions.RemoveEmptyEntries);
                        foreach (string c in conds)
                        {
                            this.Data.Add(c.Split('=')[0], c.Split('=')[1]);
                        }
                    }
                    else
                        this.Data.Add(g.Groups[1].Value.Split('=')[0], g.Groups[1].Value.Split('=')[1]);
                }
            }
        }
        /// <summary>
        /// Staticaly parses request from specified string.
        /// </summary>
        /// <param name="toParse">Specifies string which has to be parsed in HttpRequest object instance.</param>
        /// <returns>HttpRequest object instance build on string specified.</returns>
        public static HttpRequest StaticParse(string toParse)
        {
            HttpRequest req = new HttpRequest(toParse);
            return req;
        }
        /// <summary>
        /// Builds string from current request.
        /// </summary>
        /// <returns>String created from current requests' poperties.</returns>
        public string Build()
        {
            string reqfile = "";
            if (this.Method == "GET")
            {
                reqfile = this.RequestedFile;
                this.RequestedFile += "?";
                foreach (string b in this.Data.Keys)
                {
                    string het = b + "=" + this.Data[b] + "&";
                    this.RequestedFile += het;
                }
                this.RequestedFile = this.RequestedFile.Remove(this.RequestedFile.Length - 1);
            }
            string ret = String.Format("{0} {1} HTTP/{2}\r\n", this.Method, this.RequestedFile, this.HttpVersion);
            int i = 0;
            foreach (string a in this.Options.Keys)
            {
                string bet = a + ": " + this.Options[a] + Environment.NewLine;
                ret += bet;
                i++;
            }
            ret += Environment.NewLine;
            if (this.Method != "GET")
            {
                foreach (string b in this.Data.Keys)
                {
                    string het = b + "=" + this.Data[b] + "&";
                    ret += het;
                }
            }
            ret = ret.Remove(ret.Length - 1);
            ret += Environment.NewLine;
            ret += Environment.NewLine;
            this.RequestedFile = reqfile;
            return ret;
        }
        /// <summary>
        /// Staticaly builds string from HttpRequest passed.
        /// </summary>
        /// <param name="req">Request which has to be built in request string.</param>
        /// <returns>String created from request specified.</returns>
        public static string StaticBuild(HttpRequest req)
        {
            return req.Build();
        }
        /// <summary>
        /// Implicitly converts HttpRequest to Request.
        /// </summary>
        /// <param name="r">HttpRequest to be converted.</param>
        /// <returns>
        /// Result of conversion to Request.
        /// </returns>
        public static implicit operator Request(HttpRequest r)
        {
            return new Request(r.Build());
        }
        /// <summary>
        /// Performs an implicit conversion from <see cref="MadLibrary.Net.Client.Request"/> to <see cref="MadLibrary.Net.Http.HttpRequest"/>.
        /// </summary>
        /// <param name="r">The r.</param>
        /// <returns>
        /// The result of the conversion.
        /// </returns>
        public static implicit operator HttpRequest(Request r)
        {
            return new HttpRequest(r.Message);
        }

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return this.Build();
        }

        /// <summary>
        /// Builds object into string.
        /// </summary>
        /// <returns>
        /// Built string.
        /// </returns>
        string IBuildable.Build()
        {
            return this.Build();
        }

        /// <summary>
        /// Parses string into current object.
        /// </summary>
        /// <param name="input">Specifies string which has to be parsed.</param>
        void IParsable.Parse(string input)
        {
            this.Parse(input);
        }
    }
    /// <summary>
    /// Parses, holds and builds normal HTTP web response.
    /// </summary>
    public class HttpResponse : IBuildable, IParsable
    {
        /// <summary>
        /// Gets or sets requests' HTTP protocol version.
        /// </summary>
        public string HttpVersion { get; set; }
        /// <summary>
        /// Gets or sets responses status code.
        /// </summary>
        public int StatusCode { get; set; }
        /// <summary>
        /// Gets or sets responses status message.
        /// </summary>
        public string StatusMessage { get; set; }
        /// <summary>
        /// Gets or sets content of response.
        /// </summary>
        public string Content { get; set; }
        /// <summary>
        /// Holds collection of response options.
        /// </summary>
        public Dictionary<string, string> Options { get; set; }
        /// <summary>
        /// Creates new HttpResponse object instance.
        /// </summary>
        public HttpResponse()
        {
            this.HttpVersion = "";
            this.StatusCode = 0;
            this.StatusMessage = "";
            this.Options = new Dictionary<string, string>();
        }
        /// <summary>
        /// Creates new HttpResponse object instance from string specified.
        /// </summary>
        /// <param name="toParse">Specifies string which has to be parsed for new object instance creation.</param>
        public HttpResponse(string toParse)
        {
            this.Options = new Dictionary<string, string>();
            this.Parse(toParse);
        }
        /// <summary>
        /// Parses string into current response.
        /// </summary>
        /// <param name="toParse">Specifies string which has to be parsed.</param>
        public void Parse(string toParse)
        {
            Regex r = new Regex("HTTP/(\\d\\.\\d) (\\d\\d\\d) ([^\\r\\n]*)\\r\\n(.*)", RegexOptions.Singleline);
            Match m = r.Match(toParse);
            if (!m.Success)
                throw new InvalidResponseInput();
            this.HttpVersion = m.Groups[1].Value;
            this.StatusCode = int.Parse(m.Groups[2].Value);
            this.StatusMessage = m.Groups[3].Value;
            foreach (Match a in new Regex("(.*): (.*)\\r\\n").Matches(m.Groups[4].Value))
            {
                this.Options.Add(a.Groups[1].Value, a.Groups[2].Value);
            }
            r = new Regex("\\r\\n\\r\\n(.*)\\r\\n\\r\\n", RegexOptions.Singleline);
            m = r.Match(toParse);
            if (m.Success)
            {
                this.Content = m.Groups[1].Value;
            }
        }
        /// <summary>
        /// Staticaly parses string into new HttpResponse object instance.
        /// </summary>
        /// <param name="toParse">Specifies string which has to be parsed into new object instance.</param>
        /// <returns>New object instance based on specified string.</returns>
        public static HttpResponse StaticParse(string toParse)
        {
            return new HttpResponse(toParse);
        }
        /// <summary>
        /// Builds string based on current responses properties.
        /// </summary>
        /// <returns>String based on current responses properties.</returns>
        public string Build()
        {
            string ret = String.Format("HTTP/{0} {1} {2}\r\n", 
                this.HttpVersion, this.StatusCode.ToString(), this.StatusMessage);
            foreach (string a in this.Options.Keys)
            {
                ret += String.Format("{0}: {1}" + Environment.NewLine, a, this.Options[a]);
            }
            ret += Environment.NewLine;
            ret += Content;
            return ret += Environment.NewLine;
        }
        /// <summary>
        /// Staticaly builds new string based on specified HttpResponse object instance.
        /// </summary>
        /// <param name="response">Specifies response on which string has to be built.</param>
        /// <returns>String based on response specified.</returns>
        public static string StaticBuild(HttpResponse response)
        {
            return response.Build();
        }
        /// <summary>
        /// Performs an implicit conversion from <see cref="MadLibrary.Net.Http.HttpResponse"/> to <see cref="MadLibrary.Net.Client.Response"/>.
        /// </summary>
        /// <param name="r">The r.</param>
        /// <returns>
        /// The result of the conversion.
        /// </returns>
        public static implicit operator Response(HttpResponse r)
        {
            return new Response(r.Build());
        }
        /// <summary>
        /// Performs an implicit conversion from <see cref="MadLibrary.Net.Client.Response"/> to <see cref="MadLibrary.Net.Http.HttpResponse"/>.
        /// </summary>
        /// <param name="s">The s.</param>
        /// <returns>
        /// The result of the conversion.
        /// </returns>
        public static implicit operator HttpResponse(Response s)
        {
            return new HttpResponse(s.Message);
        }

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return this.Build();
        }

        /// <summary>
        /// Builds object into string.
        /// </summary>
        /// <returns>
        /// Built string.
        /// </returns>
        string IBuildable.Build()
        {
            return this.Build();
        }

        /// <summary>
        /// Parses string into current object.
        /// </summary>
        /// <param name="input">Specifies string which has to be parsed.</param>
        void IParsable.Parse(string input)
        {
            this.Parse(input);
        }
    }
}
